#ifndef __XZ_DEC_H__
#define __XZ_DEC_H__

#include <stdlib.h>
#include "xz.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef int (*xz_in_fn)(void *hdl, void *buf, int in_pos, int size);
typedef int (*xz_out_fn)(void *hdl, int out_pos, const void *buf, int size);

/**
 * @brief 执行 xz 解压过程
 *
 * @param xz_out 输出解压后的数据
 * @param xz_in 执行读取压缩的数据
 * @return int >0 已解压的数据长度
 * @return int -1 动态申请的内存不足
 * @return int -2 执行解压失败
 * @return int -3 输入或输出的错误
 */
static inline int xz_decode(xz_out_fn xz_out, void *out_hdl, xz_in_fn xz_in, void *in_hdl)
{
#define XZ_DEC_INBUF_SIZE (1024 * 2)
#define XZ_DEC_OUTBUF_SIZE (1024 * 4)
#define XZ_DEC_DICT_MAX (1024 * 32)

    struct xz_dec *s;
    struct xz_buf b;
    enum xz_ret xzret;
    int ret = -1;

    uint32_t in_total = 0;
    uint32_t out_total = 0;

    s = xz_dec_init(XZ_DYNALLOC, XZ_DEC_DICT_MAX);
    if (!s)
    {
        return -1;
    }

    uint8_t *in_buf = (uint8_t *)malloc(XZ_DEC_INBUF_SIZE);
    if (in_buf == NULL)
    {
        xz_dec_end(s);
        return -1;
    }

    uint8_t *out_buf = (uint8_t *)malloc(XZ_DEC_OUTBUF_SIZE);
    if (out_buf == NULL)
    {
        free(in_buf);
        xz_dec_end(s);
        return -1;
    }

    b.in = in_buf;
    b.in_pos = 0;
    b.in_size = 0;
    b.out = out_buf;
    b.out_pos = 0;
    b.out_size = XZ_DEC_OUTBUF_SIZE;

    while (1)
    {
        if (b.in_pos == b.in_size)
        {
            int read_size = xz_in(in_hdl, (void *)b.in, in_total, XZ_DEC_INBUF_SIZE); // 读取源文件数据
            if (read_size <= 0)
            {
                ret = -3;
                break;
            }

            b.in_pos = 0;
            b.in_size = read_size;
            in_total += read_size;
        }

        xzret = xz_dec_run(s, &b); // 执行解压
        if (xzret == XZ_OK || xzret == XZ_STREAM_END)
        {
            int write_size = xz_out(out_hdl, out_total, b.out, b.out_pos); // 输出解压后的文件数据
            if (write_size < 0)
            {
                ret = -3;
                break;
            }
            out_total += b.out_pos;
        }
        if (xzret == XZ_OK)
        {
            b.out_pos = 0;
            continue;
        }
        else if (xzret == XZ_STREAM_END)
        {
            ret = out_total;
            break;
        }
        else
        {
            ret = -2;
            break;
        }
    }

    xz_dec_end(s);

    free(in_buf);
    free(out_buf);

    return ret;

#undef XZ_DEC_INBUF_SIZE
#undef XZ_DEC_OUTBUF_SIZE
#undef XZ_DEC_DICT_MAX
}

#ifdef __cplusplus
}
#endif

#endif
